package engine.game;

import engine.entities.Entity;
import engine.entities.Mob.MobSpriteSet;
import engine.entities.MobClass.ChampionClass;
import engine.entities.MobClass.MobClass;
import engine.entities.Player;
import engine.entities.attribute.MobStatSet;
import engine.entities.attribute.StatAttribute;
import engine.entities.inventory.ContainerInventory;
import engine.entities.inventory.InventoryRender;
import engine.entities.inventory.container.ItemContainer;
import engine.entities.inventory.PartyInventory;
import engine.entities.inventory.container.ItemChest;
import engine.entities.inventory.item.InventoryItem;
import engine.entities.inventory.item.MobEquipSet;
import engine.entities.inventory.item.clothing.Crown;
import engine.entities.inventory.item.weapon.Sword;
import engine.entities.npc.WanderingNPC;
import engine.game.gfx.ColoredSprite;
import engine.game.gfx.Colors;
import engine.game.gfx.Font;
import engine.game.gfx.Screen;
import engine.game.gfx.Screen.ScreenSelectFilter;
import engine.game.gfx.Screen.ScreenSelectProcessor;
import engine.game.gfx.SpriteSheet;
import engine.game.hud.Hotbar;
import engine.game.hud.Sidebar;
import engine.game.level.Level;
import engine.resource.Resources;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.border.Border;
import util.Logger;

public class Game implements Runnable {

    public static final int WIDTH = 600;
    public static final int HEIGHT = WIDTH / 16 * 9;
    public static final int SCALE = 2;

    public static final int MAJOR_VERSION = 0;
    public static final int MINOR_VERSION = 2;
    public static final int REVISION = 7;
    public static final String STATE = "ALPHA";
    public static final String VERSION = MAJOR_VERSION + "." + MINOR_VERSION + "." + REVISION;
    public static final String WINDOW_TITLE = "Goblin Menace Version " + VERSION + " " + STATE;

    private final Logger logger = Logger.getDefaultLogger();

    private static Game globalGame;

    private JFrame frame;
    private Sidebar sidebar;
    private Hotbar hotbar;
    private JLayeredPane screenHolder;
    private JPanel screenHolderWrapper;
    private JPanel hotbarHolder;
    private JPanel leftSide;
    private InventoryRender invRend;

    private final GameMouseListener mouseList = new GameMouseListener();

    private boolean running = false;

    public static int[] colors = new int[216]; // 216 = 6 * 6 * 6

    private Screen screen;

    public InputHandler input;
    private GlobalKeyListener globalList;
    private Level level;

    private Player activePlayer;

    public static SpriteSheet sheet;

    public static int lastFrames;

    public static final Dimension DEFAULT_SIZE = new Dimension(500, 350);

    public static final List<Tickable> tickables = new ArrayList<>();

    public Game() {
        
    }

    public void init() {
        globalGame = this;
        int index = 0;
        for (int r = 0; r < 6; r++) {
            for (int g = 0; g < 6; g++) {
                for (int b = 0; b < 6; b++) {
                    int rr = (r * 255 / 5);
                    int gg = (g * 255 / 5);
                    int bb = (b * 255 / 5);

                    colors[index++] = rr << 16 | gg << 8 | bb;
                }
            }
        }

        sheet = Resources.getSpriteSheet("BASIC_SHEET");

        input = new InputHandler();
        globalList = new GlobalKeyListener();

        level = Resources.getLevel("TEST_LEVEL");
        initPlayer();

        screen = new Screen(WIDTH, HEIGHT, SCALE, colors, activePlayer, level);

        level.addEntity(activePlayer, new Point(0, 0));
        level.addEntity(new WanderingNPC(level, Resources.getSpriteSet("MAGUS_SPRITE_SET"), new MobEquipSet()), new Point(5, 5));
        ContainerInventory inv = new ContainerInventory();
        inv.setItem(0, 0, new InventoryItem(new Sword(), 1));
        ItemChest chest = new ItemChest(inv);
        level.addEntity(chest, new Point(7, 4));
        
        ContainerInventory inv2 = new ContainerInventory();
        inv2.setItem(0, 0, new InventoryItem(new Crown(), 1));
        ItemChest chest2 = new ItemChest(inv2);
        level.addEntity(chest2, new Point(7, 7));

        Font.initFont();

        GridBagConstraints c = new GridBagConstraints();
        frame = new JFrame(WINDOW_TITLE);

        frame.setLayout(new GridBagLayout());
        Border raisedBevel = BorderFactory.createRaisedBevelBorder();
        Border loweredBevel = BorderFactory.createLoweredBevelBorder();
        Border border = BorderFactory.createCompoundBorder(raisedBevel, loweredBevel);

        invRend = new InventoryRender(new PartyInventory(), null);
        invRend.setVisible(false);
        
        screenHolder = new JLayeredPane();
        screenHolder.setLayout(new LayeredPaneLayout(screenHolder));
        screenHolder.add(invRend, 1, 0);
        screenHolder.add(screen, 0, 0);
        screenHolderWrapper = new JPanel();
        screenHolderWrapper.setLayout(new BorderLayout());
        screenHolderWrapper.add(screenHolder, BorderLayout.CENTER);
        screenHolderWrapper.setBorder(border);
        leftSide = new JPanel();

        leftSide.setLayout(new GridBagLayout());
        c.gridx = 0;
        c.gridy = 0;
        c.weightx = 1.0;
        c.weighty = 0.98;
        c.fill = GridBagConstraints.BOTH;
        leftSide.add(screenHolderWrapper, c);

        hotbar = new Hotbar(this);
        hotbarHolder = new JPanel(new BorderLayout());
        hotbarHolder.setBorder(border);
        hotbarHolder.add(hotbar);
        c.gridx = 0;
        c.gridy = 1;
        c.weightx = 1.0;
        c.weighty = 0.02;
        c.fill = GridBagConstraints.BOTH;
        leftSide.add(hotbarHolder, c);

        sidebar = new Sidebar(activePlayer);
        c.gridx = 0;
        c.gridy = 0;
        c.weightx = 0.9;
        c.weighty = 1.0;
        c.fill = GridBagConstraints.BOTH;
        frame.add(leftSide, c);
        c.gridx = 1;
        c.gridy = 0;
        c.weightx = 0.1;
        c.weighty = 1.0;
        c.fill = GridBagConstraints.BOTH;
        frame.add(sidebar, c);
        
        finalInit();

        frame.pack();
        frame.setMinimumSize(DEFAULT_SIZE);
        frame.setExtendedState(JFrame.MAXIMIZED_BOTH);

        frame.setLocationRelativeTo(null);

        frame.addKeyListener(input);
        frame.addKeyListener(globalList);
        frame.addFocusListener(new GameCompListener(this));

        frame.addMouseListener(mouseList);
        screen.addMouseListener(mouseList);

        frame.setVisible(true);

        frame.setFocusable(true);
        frame.requestFocus();
    }

    public void finalInit() {
        this.setActivePlayer(getActivePlayer());
        this.setViewerEntity(getActivePlayer());
    }
    
    public static Game getGlobalGame() {
        return globalGame;
    }

    public void resetInputs() {
        input.resetInputs();
    }

    public void initPlayer() {
        MobSpriteSet defaultSet = new MobSpriteSet();

        defaultSet.setFrontFaceSprite(new ColoredSprite(sheet.getSprite(8, 28, 2, 2), Colors.get(-1, 111, 145, 543)));
        defaultSet.setLeftFaceSprite((new ColoredSprite(sheet.getSprite(4, 28, 2, 2), Colors.get(-1, 111, 145, 543))).flip(false, true));
        defaultSet.setBackFaceSprite(new ColoredSprite(sheet.getSprite(10, 28, 2, 2), Colors.get(-1, 111, 145, 543)));
        defaultSet.setRightFaceSprite(new ColoredSprite(sheet.getSprite(4, 28, 2, 2), Colors.get(-1, 111, 145, 543)));

        defaultSet.setFrontWalkSprite1(new ColoredSprite(sheet.getSprite(0, 28, 2, 2), Colors.get(-1, 111, 145, 543)));
        defaultSet.setFrontWalkSprite2(new ColoredSprite(sheet.getSprite(0, 28, 2, 2), Colors.get(-1, 111, 145, 543)).flip(false, true));

        defaultSet.setBackWalkSprite1(new ColoredSprite(sheet.getSprite(2, 28, 2, 2), Colors.get(-1, 111, 145, 543)));
        defaultSet.setBackWalkSprite2(new ColoredSprite(sheet.getSprite(2, 28, 2, 2), Colors.get(-1, 111, 145, 543)).flip(false, true));

        defaultSet.setRightWalkSprite(new ColoredSprite(sheet.getSprite(6, 28, 2, 2), Colors.get(-1, 111, 145, 543)));
        defaultSet.setLeftWalkSprite(new ColoredSprite(sheet.getSprite(6, 28, 2, 2), Colors.get(-1, 111, 145, 543)).flip(false, true));

        MobSpriteSet championSet = Resources.getSpriteSet("CHAMPION_SPRITE_SET");
        MobSpriteSet magusSet = Resources.getSpriteSet("MAGUS_SPRITE_SET");

        MobSpriteSet prowlerSet = new MobSpriteSet();

        prowlerSet.setFrontFaceSprite(new ColoredSprite(sheet.getSprite(16, 22, 2, 2), Colors.get(-1, 111, 222, 543)));
        prowlerSet.setLeftFaceSprite((new ColoredSprite(sheet.getSprite(12, 22, 2, 2), Colors.get(-1, 111, 222, 543))));
        prowlerSet.setBackFaceSprite(new ColoredSprite(sheet.getSprite(18, 22, 2, 2), Colors.get(-1, 111, 222, 543)));
        prowlerSet.setRightFaceSprite(new ColoredSprite(sheet.getSprite(8, 22, 2, 2), Colors.get(-1, 111, 222, 543)));

        prowlerSet.setFrontWalkSprite1(new ColoredSprite(sheet.getSprite(0, 22, 2, 2), Colors.get(-1, 111, 222, 543)));
        prowlerSet.setFrontWalkSprite2(new ColoredSprite(sheet.getSprite(2, 22, 2, 2), Colors.get(-1, 111, 222, 543)));

        prowlerSet.setBackWalkSprite1(new ColoredSprite(sheet.getSprite(4, 22, 2, 2), Colors.get(-1, 111, 222, 543)));
        prowlerSet.setBackWalkSprite2(new ColoredSprite(sheet.getSprite(6, 22, 2, 2), Colors.get(-1, 111, 222, 543)));

        prowlerSet.setRightWalkSprite(new ColoredSprite(sheet.getSprite(10, 22, 2, 2), Colors.get(-1, 111, 222, 543)));
        prowlerSet.setLeftWalkSprite(new ColoredSprite(sheet.getSprite(14, 22, 2, 2), Colors.get(-1, 111, 222, 543)));

        MobStatSet playerStats = new MobStatSet();
        playerStats.setStrength(new StatAttribute(14));
        playerStats.setVigor(new StatAttribute(16));
        playerStats.setAgility(new StatAttribute(12));
        playerStats.setCharisma(new StatAttribute(12));
        playerStats.setIntelligence(new StatAttribute(14));
        playerStats.setMetamagic(new StatAttribute(12));
        playerStats.setWillpower(new StatAttribute(14));

        MobClass championClass = new ChampionClass(2);

        activePlayer = new Player(level, input, championClass, playerStats, new MobEquipSet());
    }

    public synchronized void start() {
        running = true;
        new Thread(this).start();
    }

    public synchronized void stop() {
        running = false;
    }

    @Override
    public void run() {
        init();

        long lastTime = System.nanoTime();
        double nsPerTick = 1E9 / 60.0;

//      int ticks = 0;
        int frames = 0;

        long lastTimer = System.currentTimeMillis();
        double delta = 0.0;

        while (running) {
            long now = System.nanoTime();
            delta += (now - lastTime) / nsPerTick;
            lastTime = now;
            boolean shouldRender = false;

            while (delta >= 1) {
//		ticks++;
                tick();
                delta -= 1.0;
                shouldRender = true;
            }

            if (shouldRender) {
                frames++;
                render();
            }

            if (System.currentTimeMillis() - lastTimer >= 1E3) {
                lastTimer += 1000;
//		System.out.println(ticks + " UPS, " + frames + " FPS");
                lastFrames = frames;
                frames = 0;
//		ticks = 0;
            }

            // Saves CPU cycles
            now = System.nanoTime();
            delta += (now - lastTime) / nsPerTick;
            lastTime = now;
            if (delta < 0.95) {
                try {
                    Thread.yield();
                    Thread.sleep(Math.round(Math.floor(1E3 / 60 * (1 - delta) * 0.90)));
                }
                catch (InterruptedException e) {
                    logger.log(e);
                }
            }
            else if (delta < 1.0) {
                try {
                    Thread.yield();
                    Thread.sleep(1);
                }
                catch (InterruptedException e) {
                    logger.log(e);
                }
            }
            // Check to see if we should continue running
            running = frame.isVisible();
        }
    }

    public boolean isRunning() {
        return running;
    }

    public void tick() {
        level.tick();
        sidebar.tick();

        boolean complete = false;
        while (!complete) {
            try {
                for (Tickable tickable : tickables) {
                    tickable.tick();
                }
                complete = true;
            }
            catch (ConcurrentModificationException ex) {
                logger.log(ex);
            }
        }
    }

    public static void addTickable(Tickable tickable) {
        if (!tickables.contains(tickable)) {
            tickables.add(tickable);
        }
    }

    public static void removeTickable(Tickable tickable) {
        if (tickables.contains(tickable)) {
            tickables.remove(tickable);
        }
    }

    public void render() {
        screen.render(level);
        hotbar.render();
    }

    public Player getActivePlayer() {
        return activePlayer;
    }

    public void requestFrameFocus() {
        frame.requestFocus();
    }

    public void startSelection(ScreenSelectFilter filter, ScreenSelectProcessor proc) {
        screen.startSelect(filter, proc);
    }
    
    public void stopSelection() {
        screen.stopSelection();
    }
    
    public void setInventory(boolean showInv) {
        invRend.setVisible(showInv);
        if (!showInv) {
            invRend.setShowContainer(false);
        }
    }
    
    public void toggleInventory() {
        setInventory(!invRend.isVisible());
    }
    
    public boolean inventoryOpen() {
        return invRend.isVisible();
    }
    
    public void setContainer(ItemContainer container) {
        invRend.setContainer(container);
    }
    
    public void openContainer(ItemContainer container) {
        this.setContainer(container);
        invRend.setShowContainer(true);
        this.setInventory(true);
    }
    
    public void closeContainer() {
        setContainer(null);
    }
    
    public void setActivePlayer(Player player) {
        activePlayer = player;
        invRend.setPlayer(0, player);
    }
    
    public void setViewerEntity(Entity ent) {
        sidebar.setViewerEntity(ent);
    }
    
    public Entity getViewerEntity() {
        return sidebar.getViewerEntity();
    }
    
    public void addKeyListener(KeyListener listener) {
        frame.addKeyListener(listener);
    }

    public class GameCompListener implements FocusListener {

        private Game game;

        public GameCompListener(Game game) {
            this.game = game;
        }

        @Override
        public void focusGained(FocusEvent arg0) {

        }

        @Override
        public void focusLost(FocusEvent arg0) {
            game.resetInputs();
        }
    }

    public class GameMouseListener extends MouseAdapter {

        @Override
        public void mousePressed(MouseEvent e) {
            requestFrameFocus();
        }
    }

    public static class LayeredPaneLayout implements LayoutManager {

        private final Container target;
        private static final Dimension preferredSize = new Dimension(500, 500);

        public LayeredPaneLayout(final Container target) {
            this.target = target;
        }

        @Override
        public void addLayoutComponent(final String name, final Component comp) {
        }

        @Override
        public void layoutContainer(final Container container) {
            for (final Component component : container.getComponents()) {
                component.setBounds(new Rectangle(0, 0, target.getWidth(), target.getHeight()));
            }
        }

        @Override
        public Dimension minimumLayoutSize(final Container parent) {
            return preferredLayoutSize(parent);
        }

        @Override
        public Dimension preferredLayoutSize(final Container parent) {
            return preferredSize;
        }

        @Override
        public void removeLayoutComponent(final Component comp) {
        }
    }

}
